#include <ut/UnitTest.h>
#include <illib/Log.h>
#include <illib/LinkedList.h>
#include <sstream>
#include <list>
#include <string.h>

using namespace illib;
using namespace std;

namespace illib_ut{
	class UInt64{
	public:
		uint64_t value_;
		UInt64(uint64_t value) :
			value_(value)
		{

		}
		UInt64(){}
	};
	class UInt8{
	public:
		uint8_t value_;
		UInt8(uint8_t value) :
			value_(value)
		{

		}
		UInt8(){}
	};
	typedef LinkedList::List<UInt8> ListUInt8;

	template<class T> void testLinkedListAgainstExpected(LinkedList::List<T>& list, const char* indexes) {
		size_t length = strlen(indexes);
		char * reversed = new char[length+1];
		reversed[length] = '\0';
		for(size_t i = 0; i < length; i++) reversed[i] = indexes[(length-1)-i];

		//Log::debug("Testing list forwards %s \n", indexes);
		//list.debug();
		{
			stringstream stream(indexes);
			size_t number;
			size_t i = 0;
			T head(0);
			T tail(0);
			while (!stream.eof()){
				stream >> number;
				ut::test(AT(), EX(list.get(i)->value_ == number), TN(LinkedList::List::get));
				if(i == 0) head = number;
				tail = number;
				i++;
			}
			ut::test(AT(), EX(head.value_ == list.head()->value_), TN(LinkedList::List::head));
			ut::test(AT(), EX(tail.value_ == list.tail()->value_), TN(LinkedList::List::tail));
		}
		{
			stringstream stream(indexes);
			size_t number;
			LinkedList::Iterator<T> iterator(list);
			while(iterator.current()){
				stream >> number;
				ut::test(AT(), EX(iterator.current()->value_ == number), TN(LinkedList::Iterator::current));
				iterator.next();
			}
		}
		{
			stringstream stream(reversed);
			size_t number;
			LinkedList::Iterator<T> iterator(list, true);
			while(iterator.current()){
				stream >> number;
				ut::test(AT(), EX(iterator.current()->value_ == number), TN(LinkedList::Iterator::current));
				iterator.prev();
			}
		}
		{
			stringstream stream(indexes);
			size_t number;
			LinkedList::Iterator<T> iterator(list);
			while (!stream.eof()){
				stream >> number;
				//Log::debug("Expected %p, got %p\n", &elements[index], iterator.current());
				ut::test(AT(), EX(iterator.current()->value_ == number),TN(LinkedList::Iterator::current));
				iterator.next();
			}
		}
		{

			LinkedList::Iterator<T> iterator(list, true);
			size_t number;
			//Log::debug("Testing backwards %s\n", reversed);
			stringstream stream(reversed);
			while (!stream.eof()){
				stream >> number;
				//Log::debug("Expected %p, got %p\n", &elements[index], iterator.current());
				ut::test(AT(), EX(iterator.current()->value_ == number),TN(LinkedList::Iterator::current));
				iterator.prev();
			}
		}
		delete reversed;
	}

	template<class T> void testLinkedList(LinkedList::List<T> &list, Allocator & allocator){
		LinkedList::Node<T> * node;
		ut::test(AT(), EX(!list.length()),TN(LinkedList::List::length));
		ut::test(AT(), EX(!list.remove(0)),TN(LinkedList::List::remove));
		ut::test(AT(), EX(!list.add(1, allocator)),TN(LinkedList::List::add));
		ut::test(AT(), EX(!list.get(0)),TN(LinkedList::List::get));
		ut::test(AT(), EX(!list.get(1)),TN(LinkedList::List::get));
		ut::test(AT(), EX(!list.head()),TN(LinkedList::List::head));
		ut::test(AT(), EX(!list.tail()),TN(LinkedList::List::head));

		ut::test(AT(), EX(list.add(0, allocator)),TN(LinkedList::List::add));
		list.get(0)->value_ = 0;
		ut::test(AT(), EX(list.length() == 1),TN(LinkedList::List::length));
		testLinkedListAgainstExpected<T>(list,  "0");

		ut::test(AT(), EX(list.add(0, allocator)),TN(LinkedList::List::add));
		list.get(0)->value_ = 1;
		testLinkedListAgainstExpected<T>(list, "1 0");

		ut::test(AT(), EX(list.add(1, allocator)),TN(LinkedList::List::add));
		list.get(1)->value_ = 2;
		testLinkedListAgainstExpected<T>(list, "1 2 0");

		ut::test(AT(), EX(!list.add(4, allocator)),TN(LinkedList::List::add));
		ut::test(AT(), EX(list.add(3, allocator)),TN(LinkedList::List::add));
		list.get(3)->value_ = 3;
		testLinkedListAgainstExpected<T>(list, "1 2 0 3");

		ut::test(AT(), EX(list.add(2, allocator)),TN(LinkedList::List::add));
		list.get(2)->value_ = 5;
		testLinkedListAgainstExpected<T>(list, "1 2 5 0 3");

		ut::test(AT(), EX(list.add(0, allocator)),TN(LinkedList::List::add));
		list.get(0)->value_ = 6;
		testLinkedListAgainstExpected<T>(list, "6 1 2 5 0 3");

		ut::test(AT(), EX(list.add(list.length(), allocator)),TN(LinkedList::List::add));
		list.get(list.length()-1)->value_ = 7;
		testLinkedListAgainstExpected<T>(list, "6 1 2 5 0 3 7");

		ut::test(AT(), EX(list.add(6, allocator)),TN(LinkedList::List::add));
		list.get(6)->value_ = 8;
		testLinkedListAgainstExpected<T>(list, "6 1 2 5 0 3 8 7");

		ut::test(AT(), EX(node = list.remove(0)), TN(LinkedList::List::remove));
		node->destroy();
		testLinkedListAgainstExpected<T>(list, "1 2 5 0 3 8 7");

		ut::test(AT(), EX(!list.remove(7)), TN(LinkedList::List::remove));
		ut::test(AT(), EX(node = list.remove(6)), TN(LinkedList::List::remove));
		node->destroy();
		testLinkedListAgainstExpected<T>(list, "1 2 5 0 3 8");

		ut::test(AT(), EX(node = list.remove(3)), TN(LinkedList::List::remove));
		node->destroy();
		testLinkedListAgainstExpected<T>(list, "1 2 5 3 8");
		ut::test(AT(), EX(list.length() == 5),TN(LinkedList::List::length));

		ut::test(AT(), EX(list.add(list.length(), allocator)),TN(LinkedList::List::add));
		list.get(list.length()-1)->value_ = 0;
		testLinkedListAgainstExpected<T>(list, "1 2 5 3 8 0");

		ut::test(AT(), EX(list.add(0, allocator)),TN(LinkedList::List::add));
		list.get(0)->value_ = 4;
		testLinkedListAgainstExpected<T>(list, "4 1 2 5 3 8 0");

		ut::test(AT(), EX(node = list.remove(0)), TN(LinkedList::List::remove));
		node->destroy();
		testLinkedListAgainstExpected<T>(list, "1 2 5 3 8 0");

		ut::test(AT(), EX(node = list.remove(list.length()-1)), TN(LinkedList::List::remove));
		node->destroy();
		testLinkedListAgainstExpected<T>(list, "1 2 5 3 8");

		ut::test(AT(), EX(node = list.remove(2)), TN(LinkedList::List::remove));
		node->destroy();
		testLinkedListAgainstExpected<T>(list, "1 2 3 8");
	}
	void LinkedList_ut() {
		ut::UnitTestLogger logger(TN(illib_ut::LinkedList_ut));
		{
			illib::PoolAllocator::BasicAllocator allocator(System::align<LinkedList::Node<UInt8> >(), System::allocator().pagesize(), alignof(LinkedList::Node<UInt8>), 0);
			LinkedList::List<UInt8> list;
			testLinkedList<UInt8>(list, allocator);
			list.clear(&allocator);
			testLinkedList<UInt8>(list, allocator);
			list.clear(&allocator);
		}
		{
			illib::PoolAllocator::BasicAllocator allocator(System::align<LinkedList::Node<UInt64> >(), System::allocator().pagesize(), alignof(LinkedList::Node<UInt64>), 0);
			LinkedList::List<UInt64> list;
			testLinkedList<UInt64>(list, allocator);
			list.clear(&allocator);
			testLinkedList<UInt64>(list, allocator);
			list.clear(&allocator);
		}
	}
}

